#include <video_decoder.h>

static  AVDictionaryEntry decoder_options[3] = {
      { "transcode", "0" },
      { "log_level", "0" },
      { "low_delay", "1" }
};
static const char *filter_descr = "hwdownload,format=nv12";

static enum AVPixelFormat vpe_get_format(AVCodecContext *avctx, const enum AVPixelFormat *pix_fmts)
{
    return AV_PIX_FMT_VPE;
}

video_decoder::video_decoder(const char* codec_name,AVCodecParameters* codecpar,AVRational timebase)
{
    int ret = 0;
    int i =0 ;
    for (i = 0; i < FF_ARRAY_ELEMS(decoder_options); i++) {
        av_dict_set(&m_opts_dec, decoder_options[i].key, decoder_options[i].value, 0);
    }

    m_codec_name = codec_name;
    m_timebase = timebase;

    //opts_dec
    m_dec = avcodec_find_decoder_by_name(codec_name);
    if (!m_dec) {
        av_log(NULL, AV_LOG_FATAL, "Necessary decoder not found %s\n", codec_name);       
        return ;
    }
    m_dec_ctx = avcodec_alloc_context3(m_dec);
    m_dec_ctx->get_format = vpe_get_format;

    avcodec_parameters_to_context(m_dec_ctx, codecpar);
    //tran_ctx->dec_ctx->framerate = av_guess_frame_rate(tran_ctx->fmt_ctx, stream, NULL);

    ret = avcodec_open2(m_dec_ctx, m_dec, &m_opts_dec);
    av_dict_free(&m_opts_dec);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot open video decoder\n"); 
        m_open_success = false;       
    }else{
        m_open_success = true;
    }    
}

video_decoder::~video_decoder()
{
    if(m_dec_ctx)
    {
        avcodec_free_context(&m_dec_ctx);
        m_dec_ctx = nullptr;
    }
}

int  video_decoder::decode(AVPacket* pkt,AVFrame* frame,int eof,int& got,int* finished)
{
    got = 0;
    if(!m_had_read_idr ) {
        if(pkt->flags) {
            m_had_read_idr = true;
        }else {
             av_log(NULL, AV_LOG_WARNING, "Warning skip the frame,because decoder hasn't received keyframe\n");
            return 0;
        }
    }
    int ret = 0;    
    ret = avcodec_send_packet(m_dec_ctx, pkt);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Error while sending a packet to the decoder\n");
        return ret;
    }
    AVFrame* decode_frame =  av_frame_alloc();
    while (1) {
        ret = avcodec_receive_frame(m_dec_ctx, decode_frame);
        if (!eof) {
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                break;
            } else if (ret < 0) {
                av_log(NULL, AV_LOG_ERROR, "Error while receiving a frame from the decoder\n");
                return ret;
            }
        } else {
            if (ret == AVERROR_EOF) {
                *finished = 1;
                break;
            } else if (ret < 0) {
                if (ret != AVERROR(EAGAIN)) {
                    av_log(NULL, AV_LOG_ERROR, "Error while receiving a frame from the decoder\n");
                    return ret;
                } else {
                    continue;
                }
            }
        }

        decode_frame->pts = decode_frame->best_effort_timestamp;

        m_frame_dec_cnt++;
             
        if(!m_init_filters)
        {
            if ((ret = init_filters(filter_descr)) < 0)
                return ret;
            m_init_filters = true;
        }      

        if (m_download_data_from_device) {
            /* push the decoded frame into the filtergraph */
            if (av_buffersrc_add_frame_flags(m_buffersrc_ctx, decode_frame, AV_BUFFERSRC_FLAG_KEEP_REF | AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT) < 0) {
                av_log(NULL, AV_LOG_ERROR, "Error while feeding the filtergraph\n");
                return -1;
            }

            ret = av_buffersink_get_frame(m_buffersink_ctx, frame);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                break;
            if (ret < 0)
                return -1;
             got = 1;            
        }
        av_frame_unref(decode_frame);
        break;
        //tran_ctx->frame_enc_in_cnt++;
    }

    m_frame_dec_in_cnt++;
   
    return 0;
}
int  video_decoder::encode(const AVFrame*  frame, AVPacket* packet,int& got,int* finished)
{
    return 0;
}
AVMediaType video_decoder::get_codec_type() const
{   
    return AVMEDIA_TYPE_VIDEO;
}
int video_decoder::init_filters(const char *filters_spec)
{
    char args[512];
    int ret                         = 0;
    const AVFilter *buffersrc       = NULL;
    const AVFilter *buffersink      = NULL;
   
    m_filter_graph          = avfilter_graph_alloc();

    AVFilterInOut *outputs          = avfilter_inout_alloc();//tran_ctx->filter_inputs;
    AVFilterInOut *inputs           = avfilter_inout_alloc();//tran_ctx->filter_outputs;

    AVFilterGraph *filter_graph     = m_filter_graph;

    //AVCodecContext *dec_ctx;
    //AVRational time_base = m_fmt_ctx->streams[tran_ctx->video_stream_index]->time_base;
    int i;

    if (!outputs || !inputs || !filter_graph) {
        ret = AVERROR(ENOMEM);
        goto end;
    }

    av_log(NULL, AV_LOG_DEBUG, "filters_spec '%s' for stream\n", filters_spec);

    buffersrc  = avfilter_get_by_name("buffer");
    buffersink = avfilter_get_by_name("buffersink");
    if (!buffersrc || !buffersink) {
        av_log(NULL, AV_LOG_ERROR,
               "filtering source or sink element not found\n");
        ret = AVERROR_UNKNOWN;
        goto end;
    }

    snprintf(args, sizeof(args),
             "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/"
             "%d",
             m_dec_ctx->width, m_dec_ctx->height, m_dec_ctx->pix_fmt,
             m_timebase.num, m_timebase.den,
             m_dec_ctx->sample_aspect_ratio.num,
             m_dec_ctx->sample_aspect_ratio.den);
    printf("args %s\n", args);

    ret = avfilter_graph_create_filter(&m_buffersrc_ctx, buffersrc, "in",
                                       args, NULL, m_filter_graph);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot create buffer source\n");
        goto end;
    }

    ret = avfilter_graph_create_filter(&m_buffersink_ctx, buffersink, "out",
                                       NULL, NULL, filter_graph);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot create buffer sink\n");
        goto end;
    }

    /* Endpoints for the filter graph. */
    outputs->name       = av_strdup("in");
    outputs->filter_ctx = m_buffersrc_ctx;
    outputs->pad_idx    = 0;
    outputs->next       = NULL;

    inputs->name       = av_strdup("out");
    inputs->filter_ctx = m_buffersink_ctx;
    inputs->pad_idx    = 0;
    inputs->next       = NULL;

    if (!outputs->name || !inputs->name) {
        ret = AVERROR(ENOMEM);
        goto end;
    }

    av_log(NULL, AV_LOG_DEBUG, "to avfilter_graph_parse_ptr\n");

    if ((ret = avfilter_graph_parse_ptr(m_filter_graph, filters_spec, &inputs,
                                        &outputs, NULL)) < 0)
        goto end;


    av_log(NULL, AV_LOG_DEBUG, "to set hwdevice to filter\n");

    //printf("before filter dev refcnt=%d\n", av_buffer_get_ref_count(tran_ctx->dec_ctx->hw_device_ctx));
    //printf("before filter frame refcnt=%d\n", av_buffer_get_ref_count(tran_ctx->dec_ctx->hw_frames_ctx));
    if (m_dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) {
        for (i = 0; i < m_filter_graph->nb_filters; i++) {
            m_filter_graph->filters[i]->hw_device_ctx =
                av_buffer_ref(m_dec_ctx->hw_device_ctx);
	    //printf("filter_graph->filters[%d]->hw_device_ctx=%p, buffer=%p\n", i, filter_graph->filters[i]->hw_device_ctx, filter_graph->filters[i]->hw_device_ctx->buffer);
            if (!m_filter_graph->filters[i]->hw_device_ctx) {
                ret = AVERROR(ENOMEM);
                goto end;
            }
            if (strstr(m_filter_graph->filters[i]->name, "hwdownload") ||
                strstr(m_filter_graph->filters[i]->name, "vpe_pp")) {
                m_filter_graph->filters[i]->inputs[0]->hw_frames_ctx =
                    av_buffer_ref(m_dec_ctx->hw_frames_ctx);
                if (!m_filter_graph->filters[i]->inputs[0]->hw_frames_ctx) {
                    ret = AVERROR(ENOMEM);
                    goto end;
                }
            }
        }
    }
    //printf("after filter dev refcnt=%d\n", av_buffer_get_ref_count(tran_ctx->dec_ctx->hw_device_ctx));
    //printf("after filter frame refcnt=%d\n", av_buffer_get_ref_count(tran_ctx->dec_ctx->hw_frames_ctx));

    av_log(NULL, AV_LOG_DEBUG, "to avfilter_graph_config\n");

    if ((ret = avfilter_graph_config(m_filter_graph, NULL)) < 0)
        goto end;

end:
    avfilter_inout_free(&inputs);
    avfilter_inout_free(&outputs);    

    return ret;
}
